package com.cloud.proxy.filter;

import com.google.common.collect.Lists;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * PRE过滤器之限流 + 黑名单功能
 *
 * TIPS: 使用Guava RateLimiter限流。该类基于令牌桶算法实现流量限制，以达到允许某种程度的突发传输目的。
 *       令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌，而如果请求需要被处理，则需要先从桶里获取一个令牌，当桶里没有令牌可取时，则拒绝服务
 *
 *
 * 开发高并发系统时有三把利器用来保护系统：
 *      缓存 - 缓存的目的是提升系统访问速度和增大系统处理容量；
 *      降级 - 降级是当服务出现问题或者影响到核心流程时，需要暂时屏蔽掉，待高峰或者问题解决后再打开；
 *      限流 - 限流的目的是通过对并发访问/请求进行限速，或者对一个时间窗口内的请求进行限速来保护系统，
 *             一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理；
 *
 * @author qinxh
 * @date 2016/10/31
 */
@Component
public class RateLimiterFilter extends ZuulFilter {

    /**
     * 每秒产生500个令牌（请求进来后会拿去一个令牌，如果没拿到就不让访问）
     */
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(500);

    /**
     * 模拟黑名单
     */
    private static final List BLACK_LIST = Lists.newArrayList("test", "for", "bar");

    @Override
    public String filterType() {

        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {

        return -10;
    }

    /**
     * 判断是否做处理
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {

        // 获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();

        // 如果请求已经被其他过滤器终止，那么被过滤器不做处理。Zuul内置的几乎所有过滤器都会对该标志位进行判断，如果其值为false，那么将不用对
        // 请求进行过滤处理
        if(!currentContext.sendZuulResponse()){
            return Boolean.FALSE;
        }

        // 获取请求，并判断是否做处理
        HttpServletRequest request = currentContext.getRequest();
        if(request.getRequestURI().startsWith("/blackFilter/test")){
            return Boolean.TRUE;
        }

        return Boolean.FALSE;
    }

    @Override
    public Object run() {

        // 上下文对象RequestContext是共享的，所以通过RequestContext获取值
        RequestContext requestContext = RequestContext.getCurrentContext();

        // 限流如果qps访问过高（超过上面定义的，就停止访问）
        // TIPS: 可以通过Jmeter测试，Jmeter使用可见https://note.youdao.com/web/#/file/recent/note/WEBd236e88e32162dc57db6ed970478fac6/?search=jmeter
        if (!RATE_LIMITER.tryAcquire()) {
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            requestContext.getResponse().setHeader("qps", "限流");
        }

        // 判断用户是否在黑名单中，如果在，就不再转发给后端服务提供者
        String userName = requestContext.getRequest().getParameter("userName");
        if(!StringUtils.isEmpty(userName) && BLACK_LIST.contains(userName)){
            // 终止后续访问
            requestContext.setSendZuulResponse(Boolean.FALSE);
            requestContext.getResponse().setContentType("text/html;charset=utf-8");
            try {
                requestContext.getResponse().getWriter().write("黑名单");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
